package com.kingtao.service.Impl;

import com.kingtao.entry.LogEntry;
import com.kingtao.entry.Node;
import com.kingtao.entry.Peer;
import com.kingtao.service.LogService;
import com.kingtao.util.Sha256Util;
import lombok.extern.slf4j.Slf4j;
import org.rocksdb.Options;
import org.rocksdb.RocksDB;
import org.rocksdb.RocksDBException;
import org.rocksdb.WriteOptions;

import java.io.File;
import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;

/**
 * @Author: kingtao
 * @Date: 2023-07-24-15:21
 * @Description:
 */
@Slf4j
public class LogServiceImpl implements LogService {

    public String dbDir;
    public String logDir;
    private Node node;
    private RocksDB rocksDB;
    //  节点打印对应的端口
    private int port;
    ReentrantLock lock = new ReentrantLock();

    public Node getNode() {
        return node;
    }

    public void setNode(Node node) {
        this.node = node;
        this.port = this.node.getPeer().getPort();
        if (dbDir == null) {
            dbDir = "./Log/"+port;
        }
        if (logDir == null) {
            logDir = dbDir + "/logs";
        }
        RocksDB.loadLibrary();
        Options options = new Options();
        options.setCreateIfMissing(true);

        File file = new File(logDir);
        boolean success = false;
        if (!file.exists()) {
            success = file.mkdirs();
        }
        if (success) {
            log.debug("node's port {} : make a new dir {}", port, logDir);
        }
        try {
            rocksDB = RocksDB.open(logDir);
            //  防止加载 LAST_INDEX_KEY

            //  测试用的每次加载之前制空 LAST_INDEX_KEY
            //  rocksDB.delete(LAST_INDEX_KEY);
        } catch (RocksDBException e) {
            log.debug("{}", e.getMessage());
        }
    }

    public final static byte[] LAST_INDEX_KEY = "LAST_INDEX_KEY".getBytes();
    public final static byte[] PRE_INDEX = "LAST_INDEX_KEY".getBytes();


    public LogServiceImpl() {
    }

    public LogServiceImpl(Node node) {
        this.node = node;
        this.port = this.node.getPeer().getPort();
        if (dbDir == null) {
            dbDir = "./Log/"+port;
        }
        if (logDir == null) {
            logDir = dbDir + "/logs";
        }
        RocksDB.loadLibrary();
        Options options = new Options();
        options.setCreateIfMissing(true);

        File file = new File(logDir);
        boolean success = false;
        if (!file.exists()) {
            success = file.mkdirs();
        }
        if (success) {
            log.debug("node's port {} : make a new dir {}", port, logDir);
        }
        try {
            rocksDB = RocksDB.open(logDir);
            //  防止加载 LAST_INDEX_KEY

            //  测试用的每次加载之前制空 LAST_INDEX_KEY
            //  rocksDB.delete(LAST_INDEX_KEY);
        } catch (RocksDBException e) {
            log.debug("{}", e.getMessage());
        }
    }

    //  写入日志
    public void writeLogEntry(LogEntry logEntry) {
        boolean success = false;
//        System.out.println(getLastIndex());
        try {
            lock.tryLock(3000, TimeUnit.MILLISECONDS);
            logEntry.setIndex(getLastIndex()+1);
            updatePreIndex(logEntry);
            byte[] bytes = rocksDB.get(PRE_INDEX);
            logEntry.setPreIndex(new String(bytes));
            WriteOptions writeOptions = new WriteOptions().setSync(true);
            rocksDB.put(writeOptions, intToBytes(logEntry.getIndex()), logEntry.toString().getBytes());
            success =true;
        } catch (RocksDBException | InterruptedException e) {
            log.debug(e.getMessage());
        }finally {
            if (success) {
                updateLastIndexKey(logEntry.getIndex());
            }
            lock.unlock();
        }

    }

    @Override
    public LogEntry getLogByIndex(int index) {
        String[] s = null;
        try {
            byte[] bytes = rocksDB.get(intToBytes(index));
            s = (new String(bytes)).split(",");
        } catch (RocksDBException e) {
            e.printStackTrace();
        }
        return new LogEntry(Integer.parseInt(s[0]), s[1], s[2]);
    }


    //  key 对应 logEntry 的返回值
    //  key
    //  0   index
    //  1   value
    //  2   preIndex
    public String getValueFromIndexByKey(int index, int key) {
        String[] s = null;
        try {
            byte[] bytes = rocksDB.get(intToBytes(index));
            s = (new String(bytes)).split(",");
        } catch (RocksDBException e) {
            e.printStackTrace();
        }
        if (s == null) {
            return null;
        }else {
            return s[key];
        }
    }

    //  获取上次存储的索引
    public int getLastIndex() {
        byte[] lastIndex = "0".getBytes();
        try {
            lastIndex = rocksDB.get(LAST_INDEX_KEY);
            if (lastIndex == null) {
                lastIndex = "0".getBytes();
            }
        } catch (RocksDBException e) {
            e.printStackTrace();
        }
        return Integer.parseInt(new String(lastIndex));
    }

    //  更新 LAST_INDEX_KEY
    public void updateLastIndexKey(int key) {
        try {
            rocksDB.put(LAST_INDEX_KEY, intToBytes(key));
        } catch (RocksDBException e) {
            e.printStackTrace();
        }
    }


    //  获取上次的PRE_INDEX
    public String getPreIndex() {
        byte[] preIndex = Sha256Util.getSHA256("0").getBytes();
        try {
            preIndex = rocksDB.get(PRE_INDEX);
            if (preIndex == null) {
                preIndex = Sha256Util.getSHA256("0").getBytes();
            }
        } catch (RocksDBException e) {
            e.printStackTrace();
        }
        return new String(preIndex);
    }

    //  更新 LAST_INDEX_KEY
    public void updatePreIndex(LogEntry logEntry) {
        try {
            String preIndex = getPreIndex();
            String s = Sha256Util.preIndex(logEntry, preIndex);
            rocksDB.put(LAST_INDEX_KEY, s.getBytes());
        } catch (RocksDBException e) {
            e.printStackTrace();
        }
    }
    //  将 int 转化为 bytes
    public byte[] intToBytes(int temp) {
        return (temp+"").getBytes();
    }

    @Override
    public void init() {

    }

    @Override
    public void destroy() {
        rocksDB.close();
    }

    //  获取存储的全部 log
    public List<LogEntry> getAllLogEntry() {
        List<LogEntry> list = new ArrayList<>();
        for (int i=1; i <= getLastIndex(); i++) {
            list.add(getLogByIndex(i));
        }
        return list;
    }

    //  同步日志
    public void updateAllLog(List<LogEntry> list) {
        LogEntry logEntry;
        while (getLastIndex() < list.size()){
            logEntry = list.get(getLastIndex());
            writeLogEntry(logEntry);
        }
    }


    public static void main(String[] args) {
        LogServiceImpl logService = new LogServiceImpl();
        Node node = new Node(1, new Peer("127.0.0.1", 9000));
        logService.setNode(node);
        Scanner scanner = new Scanner(System.in);
        String s = "";
        while (!s.equals("quit")) {
            System.out.println("输入写入内容：");
            s = scanner.nextLine();
            LogEntry logEntry = new LogEntry(s);
            logService.writeLogEntry(logEntry);
        }
    }
}
